Skip to content

nav: band the session list by status, then by created_at, so rows stop leapfrogging - #95

Open
ellipsis-dev[bot] wants to merge 1 commit into
mainfrom
nav/band-session-list-by-status
Open

nav: band the session list by status, then by created_at, so rows stop leapfrogging#95
ellipsis-dev[bot] wants to merge 1 commit into
mainfrom
nav/band-session-list-by-status

Conversation

@ellipsis-dev

@ellipsis-dev ellipsis-dev Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The session nav — the bottom band of the multi-session TUI that a bare agent, agent "prompt", and agent session connect <id> all open — sorted open conversations first, then most-recent-event first:

const key = (s: AgentSession): number => Date.parse(lastEventAt(s)) || 0

lastEventAt reads last_activity_at, which ticks on every agent response. With several sessions in flight, each poll hands the nav a differently-permuted list: rows leapfrog continuously and the row you were reading walks out from under the cursor. The highlight itself is tracked by id, not index, so the cursor does follow its session — but the list under it keeps moving, which is the part that reads as broken.

The change

Two keys: a status band, then created_at descending inside the band.

Band Words
0 live waiting, working, starting, scheduled, retrying (+ raw running, creating_sandbox)
1 parked sleeping, idle
2 done completed, closed, and anything unrecognized
3 dead error, failed, stopped, cancelled

Two properties make this actually stop the churn:

  1. created_at is immutable. Nothing an agent does changes it, so within a band the order is fixed for the life of every row.
  2. waiting shares band 0 with the in-flight words. This is the load-bearing bit: a warm session crosses working → waiting → working on every turn (both project from AgentSessionStatus.RUNNING, split only by whether a turn is open). A band boundary there would have reproduced exactly the per-response reordering this change exists to remove. Both words mean "this conversation is live"; which side of a turn it sits on is the dot's job, not the row's position.

A row therefore moves only when a conversation genuinely parks, finishes, or dies — a handful of times per session, not once per turn.

Banding keys off rowStatusWord (the server's surface.status projection, with raw status as the fallback for pre-surface servers). isOpenConversation is dropped — the old sort was its only caller.

Note the row's age tag still counts from lastEventAt, so recency is still visible; it just no longer drives position.

Verification

Full CI equivalent locally on this branch — typecheck, test, build, and the --compile binary smoke all clean:

Test Files  23 passed (23)
     Tests  409 passed (409)

The rewritten tests assert band order, that a chatty-but-older session does not out-rank a younger one, and that flipping two live rows between working and waiting leaves the order byte-identical — the regression that would catch a future re-split of band 0.

Confirmed these are genuine regression tests rather than just coverage: swapping only sortSidebarSessions' body back to the old recency logic (keeping the new exports, so the failure is behavioural and not a missing import) fails 3 of them, including expected [ 'old', 'fresh' ] to deeply equal [ 'fresh', 'old' ] — the reported churn, reproduced.

Also checked the blast radius: sortSidebarSessions is reachable only via mergeSidebarSessions → the TUI nav, so agent session list is unaffected, and nothing in docs/ or skills/ describes the old ordering.

Provenance

Written and verified in an Ellipsis sandbox that was read-only on this repo, so it was parked in the monorepo as a handoff doc in ellipsis-dev/ellipsis#6046. This PR is that patch applied via git am (clean, no fuzz) and re-verified from scratch. Note it was authored against an older cli base — main has since moved 2 commits (#93, #94), hence 409 tests here versus the 390 the doc recorded. #6046 is being closed in favour of this PR.

🤖 Generated with Claude Code

The bottom session nav sorted by last event (open conversations first, then
most-recent-event first). With several sessions in flight that means every
agent response is a new event, so the list permuted on every poll and the row
you were reading walked out from under you.

Sort by status band, then newest-born first inside a band:

  0  live     — waiting / working / starting / scheduled / retrying
  1  parked   — sleeping / idle
  2  done     — completed / closed
  3  dead     — error / failed / stopped / cancelled

`waiting` shares the live band with the in-flight statuses deliberately: a warm
session crosses working -> waiting -> working on every turn, so banding those
apart would reproduce the churn this change exists to remove. created_at is
fixed for the life of a session, so a row only moves when it truly changes
band.

Drops isOpenConversation, whose only caller was the old sort.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants